#include "AesCrypto.h"
#include <openssl/aes.h>
#include <assert.h>

#include "Hash.h"

AesCrypto::AesCrypto(Algorithm algorithm, string key) {
    switch(algorithm) {
    case AES_ECB_128:
    case AES_CBC_128:
    case AES_CFB_128:
    case AES_OFB_128:
    case AES_CTR_128:
        assert(key.size() == 16);
        break;
    case AES_ECB_192:
    case AES_CBC_192:
    case AES_CFB_192:
    case AES_OFB_192:
    case AES_CTR_192:
        assert(key.size() == 24);
        break;
    case AES_ECB_256:
    case AES_CBC_256:
    case AES_CFB_256:
    case AES_OFB_256:
    case AES_CTR_256:
        assert(key.size() == 32);
        break;
    }

    _type = algorithm;
    _key = key;
}

AesCrypto::~AesCrypto() {}

// 加密
string AesCrypto::enCrypto(string text) {
    return aesCrypto(text, ENCRYPTO);
}

// 解密
string AesCrypto::deCrypto(string text) {
    return aesCrypto(text, DECRYPTO);
}

// [加密or解密]的具体实现
string AesCrypto::aesCrypto(string text, CryptoType crtptoType) {
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); // 实例化对称加密算法的上下文对象
    assert(ctx);

    unsigned char ivec[AES_BLOCK_SIZE]; // 对称加密算法所使用的初始向量, AES_BLOCK_SIZE = AES的密码分组长度
    generateIvec(ivec);                 // 让哈希函数以[密匙_key]为数据计算得到哈希值，并用哈希值初始化ivec

    // 初始化(加密or解密)操作的上下文ctx
    int ret = EVP_CipherInit_ex(ctx, _algorithmMap.at(_type)(), NULL,
                                    reinterpret_cast<unsigned char*>(_key.data()), ivec, crtptoType);
    /* 为什么_algorithmMap[_type]后面要加()?
     *   (1) _algorithmMap[_type]是一个函数指针类型的对象, 而期望的是[const EVP_CIPHER*对象]，
     *   (2) ∴需要调用_algorithmMap[_type]，让其返回1个[const EVP_CPIHER*的临时对象], 所以需要加()
    */
    assert(ret);

    // 准备存储数据的长度: 不是AES_BLOCK_SIZE的整数倍就需要补齐
    int length = text.size() + 1;
    if(length % AES_BLOCK_SIZE) {
        length = (length / AES_BLOCK_SIZE + 1) * AES_BLOCK_SIZE;
    }

    // 获取除最后一个分组以外的密文
    unsigned char* out = new unsigned char[length];
    int outLen = 0, totalLen = 0;
    ret = EVP_CipherUpdate(ctx, out, &outLen, reinterpret_cast<const unsigned char*>(text.data()), text.size());
    totalLen += outLen;
    assert(ret);

    // 获取最后一个分组的密文
    ret = EVP_CipherFinal_ex(ctx, out + outLen, &outLen);
    totalLen += outLen;
    assert(ret);

    string outText(reinterpret_cast<char*>(out), totalLen);
    delete[] out;
    EVP_CIPHER_CTX_free(ctx);
    return outText;
}

// 密匙 --[哈希算法]--> 哈希值 -> ivec(初始向量)
void AesCrypto::generateIvec(unsigned char *ivec) {
    // QCryptographicHash用于计算数据的哈希值
    Hash hs(HashType::Md5); // 采用Md5算法
    hs.addData(_key); // 添加数据
    string res = hs.result(Hash::Type::Binary);
    for(int i = 0; i < AES_BLOCK_SIZE; ++i) {
        ivec[i] = res.at(i);
    }
}